import 'dart:convert';
import 'dart:io';

import 'package:contextmenu/contextmenu.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart' as path;
import 'package:reorderables/reorderables.dart';
import 'package:system_tray/system_tray.dart';
import 'package:window_manager/window_manager.dart';

import './file_associated_icon.widget.dart';
import './local_app.model.dart';
import './local_setting.model.dart';
import './main.dart';
import './my_date_format.constant.dart';
import './shared/services/shared_util.service.dart';

const String settingFilename = 'my-app-home-setting.json';
typedef OnConfirmedCallback = void Function(String appTitle);

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const FluentApp(
      // locale: Locale('zh', 'CH'),
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with WindowListener {
  final FocusNode _keyboardFocusNode = FocusNode();
  final SystemTray _systemTray = SystemTray();
  final Menu _systemTrayMenu = Menu();

  final TextEditingController _keywordEditingController =
      TextEditingController();

  List<LocalApp> _appList = [];
  String _statusTitle = '';
  String _statusContent = '';
  String _keyword = '';
  bool _showTextBoxInfo = false;
  bool _isKeyboardControlPressed = false;
  String _systemTrayExitMenuLabel = '';

  @override
  void initState() {
    super.initState();

    if (isDesktop) {
      windowManager.addListener(this);
      windowManager.setPreventClose(true);
    }

    _initSystemTray();
    _loadSetting();
  }

  @override
  void dispose() {
    _keyboardFocusNode.dispose();
    _systemTray.destroy();
    _keywordEditingController.dispose();

    super.dispose();
  }

  @override
  void onWindowMinimize() {
    windowManager.setSkipTaskbar(true);
  }

  @override
  void onWindowClose() {
    windowManager.minimize();
  }

  @override
  Widget build(BuildContext context) {
    _setSystemTrayMenuLabel();

    return NavigationView(
      content: DropTarget(
        onDragDone: (detail) {
          for (final file in detail.files) {
            if (File(file.path).statSync().type == FileSystemEntityType.file) {
              _addFile(fileFullName: file.path, filename: file.name);
            }
          }
        },
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _buildCommandBarCard(),
            TextBox(
              controller: _keywordEditingController,
              autofocus: true,
              suffix: IconButton(
                icon: Icon(
                  MdiIcons.close,
                  size: 16.0,
                  color: Colors.grey[90],
                ),
                style: ButtonStyle(
                  padding: WidgetStateProperty.all<EdgeInsets>(
                    const EdgeInsets.all(4.0),
                  ),
                ),
                onPressed: () {
                  _keywordEditingController.clear();

                  if (mounted) {
                    setState(() {
                      _keyword = '';
                      _updateStatusTitle();
                    });
                  }
                },
              ),
              onChanged: (value) {
                if (mounted) {
                  setState(() {
                    _keyword = value;
                    _updateStatusTitle();
                  });
                }
              },
            ),
            Expanded(
              child: _buildAppListView(),
            ),
            // if (_statusInfo.isNotEmpty) _buildStatusBar(),
            _buildStatusBar(),
          ],
        ),
      ),
    );
  }

  /// Builds top command bar card.
  Widget _buildCommandBarCard() {
    return CommandBarCard(
      child: CommandBar(
        overflowBehavior: CommandBarOverflowBehavior.dynamicOverflow,
        primaryItems: [
          CommandBarButton(
            icon: Icon(MdiIcons.windowOpen),
            label: Text(AppLocalizations.of(context)!.runPinned),
            onPressed: _getFilteredAppList().where((p) => p.isPinned).isNotEmpty
                ? _runPinnedFiles
                : null,
          ),
          const CommandBarSeparator(),
          CommandBarButton(
            icon: const Icon(FluentIcons.add),
            label: Text(AppLocalizations.of(context)!.add),
            onPressed: _addPickedFile,
          ),
          CommandBarButton(
            icon: const Icon(FluentIcons.delete),
            label: Text(AppLocalizations.of(context)!.remove),
            onPressed: _appList.where((p) => p.isSelected).isNotEmpty
                ? _removeSelectedApps
                : null,
          ),
          CommandBarButton(
            icon: Icon(MdiIcons.autorenew),
            label: Text(AppLocalizations.of(context)!.clearBroken),
            onPressed: _clearBroken,
          ),
          CommandBarButton(
            icon: const Icon(FluentIcons.save),
            label: Text(AppLocalizations.of(context)!.save),
            onPressed: _saveSetting,
          ),
          const CommandBarSeparator(),
          CommandBarButton(
            icon: Icon(MdiIcons.exitToApp),
            label: Text(AppLocalizations.of(context)!.exit),
            onPressed: () => exit(0),
          ),
        ],
      ),
    );
  }

  /// Builds app list view.
  Widget _buildAppListView() {
    return Focus(
      focusNode: _keyboardFocusNode,
      onKeyEvent: (node, event) {
        _isKeyboardControlPressed = HardwareKeyboard.instance.isControlPressed;

        return KeyEventResult.handled;
      },
      child: SingleChildScrollView(
        child: ReorderableWrap(
          spacing: 4.0,
          runSpacing: 4.0,
          padding: const EdgeInsets.all(4.0),
          enableReorder: _keyword.isEmpty,
          onReorder: (int oldIndex, int newIndex) {
            if (mounted) {
              setState(() {
                final row = _appList.removeAt(oldIndex);
                _appList.insert(newIndex, row);
              });
            }
          },
          children:
              _getFilteredAppList().map((e) => _buildCardItem(e)).toList(),
        ),
      ),
    );
  }

  /// Builds bottom status bar.
  Widget _buildStatusBar() {
    return InfoBar(
      isIconVisible: false,
      title: Text(
        _statusTitle,
        style: const TextStyle(
          fontSize: 12.0,
        ),
      ),
      content: Text(
        _statusContent,
        style: const TextStyle(
          fontSize: 11.0,
        ),
      ),
      onClose: _statusContent.isNotEmpty
          ? () {
              if (mounted) {
                setState(() => _statusContent = '');
              }
            }
          : null,
    );
  }

  /// Builds card item border based on pin status.
  BorderSide _buildCardItemPinnedBorder(
      bool isPinned, double selectedWidth, double unselectedWidth) {
    return BorderSide(
      width: isPinned ? selectedWidth : unselectedWidth,
      color: Colors.blue.lightest,
    );
  }

  /// Builds app info in card layout.
  Widget _buildCardItem(LocalApp app) {
    return SizedBox(
      width: 110.0,
      height: 75.0,
      child: MouseRegion(
        opaque: false,
        onEnter: (event) {
          if (mounted) {
            setState(() => app.isHovering = true);
          }
        },
        onExit: (event) {
          if (mounted) {
            setState(() => app.isHovering = false);
          }
        },
        child: GestureDetector(
          onTap: () {
            _onItemTap(app);
            _keyboardFocusNode.requestFocus();
          },
          onDoubleTap: () {
            // The OnTap event triggered with slight delay when the onDoubleTap event enabled in gesture detector.
            // https://github.com/flutter/flutter/issues/50458
            // The problem with the current InkWell is the delayed start of the splash effect while using onDoubleTap

            // If you are using onTap & onDoubleTap side by side with an InkWell, then a single tap gets delayed.
            // This is necessary for the recognition of a double tap but also the splash effect is delayed. With this package the splash effect is startet instantly.

            _onItemTap(app);
            _runSelectedFiles();
          },
          onSecondaryTapUp: (details) {
            if (!app.isSelected ||
                _appList.where((p) => p.isSelected).isEmpty) {
              _onItemTap(app);

              // _keyboardFocusNode.requestFocus();
            }

            showContextMenu(details.globalPosition, context,
                (context) => _buildContextMenuItems(app), 0.0, 180.0);
          },
          child: Container(
            decoration: BoxDecoration(
              color: app.isSelected
                  ? const Color.fromARGB(255, 79, 171, 241)
                  : Colors.white,
              border: Border(
                bottom: _buildCardItemPinnedBorder(app.isPinned, 10.0, 2.0),
                left: _buildCardItemPinnedBorder(app.isPinned, 1.0, 0.0),
                top: _buildCardItemPinnedBorder(app.isPinned, 1.0, 0.0),
                right: _buildCardItemPinnedBorder(app.isPinned, 1.0, 0.0),
              ),
            ),
            child: Card(
              padding: const EdgeInsets.all(4.0),
              child: Stack(
                alignment: AlignmentDirectional.center,
                children: [
                  // Bottom item displays on the top in the [Stack] widget.
                  // If this [Tooltip] is on the top of the icon it will not work.
                  Visibility(
                    visible: app.isHovering,
                    child: Positioned(
                      top: 2.0,
                      right: 2.0,
                      child: Tooltip(
                        // message: '${app.title}\n${app.fileFullName}',
                        richMessage: TextSpan(
                          text: '${app.title}\n',
                          style: const TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 14.0,
                          ),
                          children: [
                            TextSpan(
                              text: app.fileFullName,
                              style: const TextStyle(
                                fontWeight: FontWeight.normal,
                                fontSize: 12.0,
                              ),
                            ),
                          ],
                        ),
                        child: GestureDetector(
                          onTap: () => _runFile(app.fileFullName),
                          child: Icon(MdiIcons.informationOutline),
                        ),
                      ),
                    ),
                  ),
                  Visibility(
                    visible: app.isHovering || app.isPinned,
                    child: Positioned(
                      top: 3.0,
                      left: 2.0,
                      child: GestureDetector(
                        child: app.isPinned
                            ? Icon(
                                MdiIcons.pin,
                                color: Colors.grey[130],
                              )
                            : Icon(MdiIcons.pinOutline),
                        onTap: () {
                          if (mounted) {
                            setState(() {
                              app.isPinned = !app.isPinned;
                              _updateStatusTitle();
                            });
                          }
                        },
                      ),
                    ),
                  ),
                  Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      FileAssociatedIcon(
                        app.fileFullName,
                        key: ValueKey(app.fileFullName),
                      ),
                      const SizedBox(height: 4.0),
                      Text(
                        app.title,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(fontSize: 12.0),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  /// Builds context menu for each item.
  List<Widget> _buildContextMenuItems(LocalApp app) {
    const divider = Divider(
      style: DividerThemeData(
        thickness: 0.2,
        decoration: BoxDecoration(
          color: Colors.black,
        ),
      ),
    );

    const contextMenuIconSize = 18.0;

    return [
      ListTile(
        leading: Icon(
          MdiIcons.windowOpen,
          size: contextMenuIconSize,
        ),
        title: Text(AppLocalizations.of(context)!.runSelected),
        onPressed: () {
          Navigator.pop(context);

          _runSelectedFiles();
        },
      ),
      ListTile(
        leading: Icon(
          MdiIcons.folderOpenOutline,
          size: contextMenuIconSize,
        ),
        title: Text(AppLocalizations.of(context)!.openDirectory),
        onPressed: () {
          Navigator.pop(context);

          if (Directory(app.filePath).existsSync()) {
            OpenFile.open(app.filePath);
          }
        },
      ),
      divider,
      ListTile(
        leading: Icon(
          MdiIcons.textBoxEditOutline,
          size: contextMenuIconSize,
        ),
        title: Text(AppLocalizations.of(context)!.edit),
        onPressed: () {
          Navigator.pop(context);

          _showEditDialog(app.title, (appTitle) {
            if (mounted) {
              setState(() {
                app.title = appTitle;
              });
            }
          });
        },
      ),
      ListTile(
        leading: const Icon(
          FluentIcons.delete,
          size: contextMenuIconSize,
        ),
        title: Text(AppLocalizations.of(context)!.remove),
        onPressed: () {
          Navigator.pop(context);
          _removeSelectedApps();
        },
      ),
      divider,
      ListTile(
        leading: const Icon(
          FluentIcons.save,
          size: contextMenuIconSize,
        ),
        title: Text(AppLocalizations.of(context)!.saveSettings),
        onPressed: () {
          Navigator.pop(context);
          _saveSetting();
        },
      ),
      ListTile(
        leading: Icon(
          MdiIcons.exitToApp,
          size: contextMenuIconSize,
        ),
        title: Text(AppLocalizations.of(context)!.exit),
        onPressed: () {
          Navigator.pop(context);
          exit(0);
        },
      ),
    ];
  }

  List<LocalApp> _getFilteredAppList() {
    return _appList
        .where((p) =>
            p.title.toLowerCase().startsWith(_keyword.trim().toLowerCase()))
        .toList();
  }

  void _onItemTap(LocalApp app) {
    if (mounted) {
      setState(() {
        _changeSelection(app);
        _updateStatusTitle();
      });
    }
  }

  /// Changes each app selection status in list.
  void _changeSelection(LocalApp app) {
    if (!_isKeyboardControlPressed) {
      for (final p in _appList) {
        p.isSelected = false;
      }
    }

    app.isSelected = !app.isSelected;
  }

  /// Updates bottom status bar title.
  void _updateStatusTitle() {
    _statusTitle = AppLocalizations.of(context)!.summaryInfo(
        _getFilteredAppList().where((e) => e.isPinned == true).length,
        _getFilteredAppList().where((e) => e.isSelected == true).length,
        _getFilteredAppList().length);
  }

  void _runPinnedFiles() {
    for (final file in _getFilteredAppList()) {
      if (file.isPinned) {
        _runFile(file.fileFullName);
      }
    }
  }

  void _runSelectedFiles() {
    for (final file in _getFilteredAppList()) {
      if (file.isSelected) {
        _runFile(file.fileFullName);
      }
    }
  }

  /// Runs file with its full name.
  void _runFile(String fileFullName) {
    if (File(fileFullName).existsSync()) {
      OpenFile.open(fileFullName);
    }
  }

  /// Picks and Adds file to list.
  Future<void> _addPickedFile() async {
    final result = await FilePicker.platform.pickFiles();

    if (result?.files.isNotEmpty == true && mounted) {
      final file = result!.files.first;
      _addFile(fileFullName: file.path!, filename: file.name);
    }
  }

  /// Adds file to list.
  void _addFile({required String fileFullName, required String filename}) {
    if (_appList.where((p) => p.fileFullName == fileFullName).isEmpty) {
      _showEditDialog(filename.substring(0, filename.indexOf('.')), (appTitle) {
        // Gets file path without filename.
        final filePath = fileFullName.substring(
            0, fileFullName.lastIndexOf(Platform.pathSeparator));

        if (mounted) {
          setState(() {
            _appList.add(LocalApp(
                fileFullName: fileFullName,
                filePath: filePath,
                title: appTitle));

            _updateStatusTitle();
          });
        }
      });
    } else {
      _showStatusInfo(AppLocalizations.of(context)!.fileExists(fileFullName));
    }
  }

  /// Removes all selected apps from list.
  void _removeSelectedApps() {
    final selectedApps = _getFilteredAppList().where((p) => p.isSelected);

    _showConfirmDialog(
        AppLocalizations.of(context)!.removeConfirm(selectedApps.length == 1
            ? selectedApps.first.title
            : "${selectedApps.length} apps"), () {
      if (mounted) {
        setState(() {
          for (final app in selectedApps) {
            _appList.remove(app);
          }

          _updateStatusTitle();
        });
      }
    });
  }

  /// Shows edit dialog for app info.
  void _showEditDialog(String appTitle, OnConfirmedCallback onConfirm) {
    final titleController = TextEditingController();

    // https://skillypro.com/flutter-how-to-call-setstate-in-alert-dialog-2022/
    showDialog(
      context: context,
      builder: (context) => StatefulBuilder(
        builder: (context, dialogSetState) {
          return ContentDialog(
            // title: const Text('Define title'),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Text(AppLocalizations.of(context)!.defineTitle),
                const SizedBox(height: 4.0),
                TextBox(
                  autofocus: true,
                  controller: titleController..text = appTitle,
                  onSubmitted: (value) =>
                      _confirmEdit(titleController, dialogSetState, onConfirm),
                ),
                const SizedBox(height: 10.0),
                if (_showTextBoxInfo)
                  InfoBar(
                    title: Text(AppLocalizations.of(context)!.inputTip),
                  ),
              ],
            ),
            actions: [
              FilledButton(
                child: Text(AppLocalizations.of(context)!.confirm),
                onPressed: () =>
                    _confirmEdit(titleController, dialogSetState, onConfirm),
              ),
              Button(
                onPressed: () {
                  if (mounted) {
                    dialogSetState(() {
                      _showTextBoxInfo = false;
                    });
                  }

                  titleController.clear();
                  Navigator.pop(context);
                },
                child: Text(AppLocalizations.of(context)!.cancel),
              ),
            ],
          );
        },
      ),
    );
  }

  /// Confirms file info edit.
  void _confirmEdit(TextEditingController titleController,
      StateSetter dialogSetState, OnConfirmedCallback onConfirm) {
    if (titleController.text.isEmpty) {
      if (mounted) {
        dialogSetState(() => _showTextBoxInfo = true);
      }
    } else {
      if (mounted) {
        dialogSetState(() {
          _showTextBoxInfo = false;
        });

        onConfirm(titleController.text);
      }

      titleController.clear();
      Navigator.pop(context);
    }
  }

  /// Gets system tray image file path.
  String _getTrayImagePath(String imageName) {
    return Platform.isWindows
        ? 'assets/images/$imageName.ico'
        : 'assets/images/$imageName.png';
  }

  /// We want to change the label once locale changes and UI rebuilds.
  /// That's why we need to call this method in [build] instead of [iniState];
  void _setSystemTrayMenuLabel() {
    if (AppLocalizations.of(context)!.exit != _systemTrayExitMenuLabel) {
      _systemTrayExitMenuLabel = AppLocalizations.of(context)!.exit;

      _systemTrayMenu.findItemByName<MenuItemLabel>('exit')?.label =
          _systemTrayExitMenuLabel;
    }
  }

  // String _getTrayMenuImagePath(String imageName) {
  //   return Platform.isWindows
  //       ? 'assets/images/$imageName.bmp'
  //       : 'assets/images/$imageName.png';
  // }

  /// Inits system tray setting.
  Future<void> _initSystemTray() async {
    await _systemTrayMenu.buildFrom([
      MenuItemLabel(
        name: 'exit',
        label: _systemTrayExitMenuLabel,
        onClicked: (menuItem) => exit(0),
      ),
    ]);

    // We first init the systray menu and then add the menu entries
    await _systemTray.initSystemTray(iconPath: _getTrayImagePath('logo'));
    _systemTray.setToolTip("My App Home");

    // handle system tray event
    _systemTray.registerSystemTrayEventHandler((eventName) {
      if (eventName == kSystemTrayEventClick) {
        windowManager.setSkipTaskbar(false);
        windowManager.show();
      } else if (eventName == kSystemTrayEventRightClick) {
        _systemTray.popUpContextMenu();
      }
    });

    _systemTray.setContextMenu(_systemTrayMenu);
  }

  /// Show confirm dialog for deletion.
  void _showConfirmDialog(String content, VoidCallback onConfirm) {
    showDialog<String>(
      context: context,
      builder: (context) => ContentDialog(
        content: Text(content),
        actions: [
          FilledButton(
            child: Text(AppLocalizations.of(context)!.remove),
            onPressed: () {
              Navigator.pop(context);
              onConfirm();
            },
          ),
          Button(
            child: Text(AppLocalizations.of(context)!.cancel),
            onPressed: () => Navigator.pop(context),
          ),
        ],
      ),
    );
  }

  /// Loads local app setting.
  Future<void> _loadSetting() async {
    // Gets setting file full name.
    final settingFilePath = _getSettingFileFullName();
    final settingFile = File(settingFilePath);

    if (await settingFile.exists()) {
      final jsonString = await settingFile.readAsString();
      final jsonData = json.decode(jsonString);
      final setting = LocalSetting.fromJson(jsonData);

      if (mounted) {
        setState(() => _appList = setting.appList ?? []);
      }

      _updateStatusTitle();
    }
  }

  /// Clears all apps with broken path.
  void _clearBroken() {
    List<LocalApp> missingApps = [];

    for (final p in _appList) {
      if (!File(p.fileFullName).existsSync()) {
        missingApps.add(p);
      }
    }

    for (final app in missingApps) {
      _appList.remove(app);
    }

    _updateStatusTitle();

    _showStatusInfo(AppLocalizations.of(context)!
        .brokenClearInfo(_getCurrentTimeString(), missingApps.length));
  }

  /// Saves setting to local json format file.
  Future<void> _saveSetting() async {
    // Prepares setting json format data.
    final setting = LocalSetting(appList: _appList);
    final jsonData = json.encode(setting.toJson());

    // Gets setting file full name.
    final settingFilePath = _getSettingFileFullName();

    // Saves to file.
    await File(settingFilePath).writeAsString(jsonData);

    if (mounted) {
      _showStatusInfo(AppLocalizations.of(context)!
          .settingSaveInfo(_getCurrentTimeString(), settingFilePath));
    }
  }

  /// Shows info on bottom status bar.
  void _showStatusInfo(String info) {
    if (mounted) {
      setState(() => _statusContent = info);
    }
  }

  /// Gets current execuable path combined with setting filename.
  String _getSettingFileFullName() {
    return path.join(SharedUtil.getExecutablePath(), settingFilename);
  }

  /// Gets current time string as HH:mm format.
  String _getCurrentTimeString() {
    return SharedUtil.formatDate(
        SharedUtil.getCurrentDateTime(), MyDateFormat.longTime);
  }
}
